/**
 * Copyright (c) 2010, Todd Ginsberg
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the name of Todd Ginsberg, or Gowalla nor the
 *      names of any contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 *  Also, please use this for Good and not Evil.  
 */
package com.ginsberg.gowalla.oauth;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import com.ginsberg.gowalla.Version;
import com.ginsberg.gowalla.auth.OAuth2Authentication;
import com.ginsberg.gowalla.dto.oauth.OAuthAccessTokenResponse;
import com.ginsberg.gowalla.exception.GowallaRequestException;
import com.ginsberg.gowalla.request.DefaultRequestHandler;
import com.ginsberg.gowalla.request.PlainRequestHeader;
import com.ginsberg.gowalla.request.RequestHandler;
import com.ginsberg.gowalla.request.RequestHeader;
import com.ginsberg.gowalla.request.RequestParameter;
import com.ginsberg.gowalla.request.StringRequestParameter;
import com.ginsberg.gowalla.request.translate.GsonResponseTranslator;
import com.ginsberg.gowalla.request.translate.ResponseTranslator;

/**
 * This is a class to help you work with Gowalla's OAuth2 system.  It is not 
 * essential that you use it if you already have OAuth2 code that you prefer.
 * Essentially, at the end you want an authorization token and a refresh token.
 * 
 * Before integrating with Gowalla for read/write access using OAuth, you'll
 * have to go register your application.   This can be done at http://www.gowalla.com/api
 * for free.  Be sure to copy down the secret key and the api key they provide. 
 * Also, the callback url is required and is limited to one per app.  
 * You might want more than one key so you can run your code in more than one 
 * environment (dev, test, production) and have different callbacks.
 * 
 * Workflow:
 * 1) Request access (createAuthorizationRequest() method).  This provides a
 * URL that you redirect your user to.  The user will either allow or deny
 * the request after being authenticated by Gowalla.  Gowalla will then
 * redirect the user back to your callback (assuming everything worked).  
 * 
 * 2) The callback code (which you write) needs to look for the "code"
 * parameter.  This is what you use to perform the next step of the workflow.
 * 
 * 3) Request Access Token (requestAccessToken method).  Using the code from step 2, the OAuth object will
 * make a post request to Gowalla and request an access token for you.  The return
 * object will contain the access token, a refresh token, the user being authenticated,
 * and a time in seconds until the access token expires.
 * 
 * 4) Refresh Access Token (refreshAccessToken method).  After two weeks or so your access 
 * token will expire.  In order to get a new one, you can ask Gowalla for a new one if you
 * kept the refresh token from step 3.  This call returns the same information an access request 
 * call does.
 * 
 * Summary:
 * 1) Keep your API Key and Secret Key in a safe place.
 * 2) For each user, store their access token, refresh token, and time the refresh token expires.
 * 3) Having the user authorize your app only needs to happen once unless they revoke your 
 * permission at some point.  Once you have a valid auth, you just need to get an access token.
 * 4) Use this for good and not evil. 
 * 
 * Example: Request Authoriation
 * <pre>
 * {@code
 * private final static String API_KEY = "1234...";
 * private final static String SECRET_KEY = "abcde...";
 * private final static String CALLBACK = "http://localhost/callback";
 * 
 * OAuth oauth - new OAuth(API_KEY, SECRET_KEY, CALLBACK, Scope.READ_WRITE);
 * String authRequestRedirect = oauth.createAuthorizationRequest();
 * // Redirect your user to the authRequestRedirect URL.
 * }
 * </pre>
 * 
 * Example: Request Access
 * <pre>
 * {@code
 * private final static String API_KEY = "1234...";
 * private final static String SECRET_KEY = "abcde...";
 * private final static String CALLBACK = "http://localhost/callback";
 * 
 * // Assumption here is that you have parsed the "code" parameter
 * // out of the callback from the previous step.
 * OAuth oauth - new OAuth(API_KEY, SECRET_KEY, CALLBACK, Scope.READ_WRITE);
 * OAuthAccessTokenResponse token = requestAccessToken(code);
 * 
 * // NOTE: Store the token here.  
 * // Request data with the token.
 * Authentication auth = new OAuth2Authorization(token.getAccessToken());
 * Gowalla gowalla = new Gowalla("ExampleApp", auth);
 * // Now to whatever you had in mind with the Gowalla app.
 * }
 * </pre>
 * 
 * @author Todd Ginsberg
 */
public class OAuth {

	private String apiKey; 
	private String secretKey; 
	private String callbackURL;
	private RequestHandler handler = new DefaultRequestHandler(); 
	private ResponseTranslator translator = new GsonResponseTranslator();
	private Scope scope = Scope.READ;
	
	public enum Scope {
		READ("read"),
		READ_WRITE("read-write");
		private String val = null;
		private Scope(String val) {
			this.val = val;
		}
		public String getValue() { return val; }
	}
	
	/**
	 * @param apiKey The api key Gowalla has given you.
	 * @param secretKey The secret key Gowalla has given you.
	 * @param callbackURL The callback url regeistered with Gowalla.
	 * @param scope Either read or read/write.
	 */
	public OAuth(final String apiKey, final String secretKey, final String callbackURL, final Scope scope) {
		super();
		this.apiKey = apiKey;
		this.secretKey = secretKey;
		this.callbackURL = callbackURL;
		this.scope = scope;
		Collection<RequestHeader> headers = new LinkedList<RequestHeader>();
		headers.add(new PlainRequestHeader("User-Agent", String.format("%s (%s)", "JavaOAuth", Version.getVersion())));
		headers.add(new PlainRequestHeader("Accept", "application/json")); 
		handler.setRequestHeaders(headers);
	}


	/**
	 * This method will create an authorization request URL.  
	 * Redirect your user to the url provided and listen on the callback.
	 * 
	 * @return A String URL to redirect to.
	 */
	public String createAuthorizationRequest() {
		return String.format("https://gowalla.com/api/oauth/new?%s&%s&%s",
				new StringRequestParameter("redirect_uri", callbackURL).format(),
				new StringRequestParameter("client_id", apiKey).format(),
				new StringRequestParameter("scope", scope.getValue()).format());
	}
	
	/**
	 * Makes an HTTP Post request to Gowalla's server to create an access
	 * token.  
	 * 
	 * @param codeFromAuthRequest Code was provided as an argument on the callback url from the authorization request.
	 * @return An OAuthAccessTokenResponse object.
	 */
	public OAuthAccessTokenResponse requestAccessToken(final String codeFromAuthRequest) throws GowallaRequestException {
		final List<RequestParameter> params = new LinkedList<RequestParameter>();
		params.add(new StringRequestParameter("client_id", apiKey));
		params.add(new StringRequestParameter("client_secret", secretKey));
		params.add(new StringRequestParameter("code", codeFromAuthRequest));
		params.add(new StringRequestParameter("redirect_uri", callbackURL));
		params.add(new StringRequestParameter("scope", scope.getValue()));
		params.add(new StringRequestParameter("grant_type", "authorization_code"));
		
		try {
			final String response = handler.postResource("/api/oauth/token", new OAuth2Authentication(null), params);
			return translator.translateOAuthAccessToken(response);
		} catch(GowallaRequestException g) {
			g.getCause().printStackTrace();
			throw g;
		}
	}
	
	/**
	 * Makes an HTTP Post request to Gowalla's server to create an access
	 * token.
	 * 
	 * @param refreshToken Code was provided as part of the requestAccessToken response object.
	 */
	public OAuthAccessTokenResponse refreshAccessToken(final String refreshToken) throws GowallaRequestException {
		final List<RequestParameter> params = new LinkedList<RequestParameter>();
		params.add(new StringRequestParameter("client_id", apiKey));
		params.add(new StringRequestParameter("client_secret", secretKey));
		params.add(new StringRequestParameter("refresh_token ", refreshToken));
		params.add(new StringRequestParameter("scope", scope.getValue()));
		params.add(new StringRequestParameter("grant_type", "refresh_token"));
		
		try {
			final String response = handler.postResource("/api/oauth/token", new OAuth2Authentication(null), params);
			return translator.translateOAuthAccessToken(response);
		} catch(GowallaRequestException g) {
			g.getCause().printStackTrace();
			throw g;
		}
	}
}